﻿using System;
using System.Collections.Specialized;
using System.Windows.Forms;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
//using System.Drawing;

namespace Transformer.iHunter.Plugins.BBS
{
    /// <summary>
    /// Implements the telnet protocol
    /// </summary>
    public class Communicator
    {
        #region Public Methods

        /// <summary>
        /// Connect to the server
        /// </summary>
        public void Connect()
        {
            System.Diagnostics.Debug.WriteLine(string.Format("Connecting {0} type {1} ...", this.hostName, this.connectionType));

            switch (this.connectionType)
            {
                case ConnectionType.Telnet:
                    {
                        this.ConnectTelnet();
                        break;
                    }
                case ConnectionType.SSH1:
                    {
                        this.ConnectSSH1();
                        break;
                    }
                case ConnectionType.SSH2:
                    {
                        break;
                    }
                default:
                    {
                        break;
                    }
            }
        }

        /// <summary>
        /// Send data to the server
        /// </summary>
        /// <param name="strText">The content to be sent</param>
        public void Send(string str)
        {
            //Console.WriteLine(strText);            

            try
            {
                /*System.Byte[] smk = new System.Byte[strText.Length];


                for (int i = 0; i < strText.Length; i++)
                {
                    System.Byte ss = System.Convert.ToByte(strText[i]);
                    smk[i] = ss;
                }*/

                byte[] smk = System.Text.Encoding.GetEncoding("gb2312").GetBytes(str);

                if (callbackEndDispatch == null)
                {
                    callbackEndDispatch = new System.AsyncCallback(EndDispatchMessage);
                }

                if (this.CurSocket == null)
                {

                    reader._pf.Transmit(smk);

                    //smk = Transpose(smk);
                }

                else
                {
                    if (!this.CurSocket.Connected)
                        throw new Exception("Connection is closed");

                    System.IAsyncResult ar = this.CurSocket.BeginSend(
                        smk,
                        0,
                        smk.Length,
                        System.Net.Sockets.SocketFlags.None,
                        callbackEndDispatch,
                        this.CurSocket);


                }

            }
            catch (System.Exception CurException)
            {
                this.OnError(CurException);
            }
        }

        

        /// <summary>
        /// Invoke the DataReceivedEventHandler after have received data
        /// </summary>
        /// <param name="data">Data received</param>
        public void OnReceivedData(byte[] data)
        {
            if (this.DataReceivedHandler != null)
                this.DataReceivedHandler(this, new DataReceivedEventArgs(data));
        }

        public void OnError(Exception exception)
        {
            if (this.ErrorHandler != null)
                this.ErrorHandler(this, exception);
        }

        public void Disconnect()
        {           

            if (this.connectionType == ConnectionType.Telnet)
            {
                if (null != CurSocket && CurSocket.Connected)
                {
                    CurSocket.Shutdown(System.Net.Sockets.SocketShutdown.Both);

                    CurSocket.Close();
                }
            }
            else if (this.connectionType == ConnectionType.SSH1)
            {
                System.Diagnostics.Debug.WriteLine("To disconnect ssh");
                this.reader.Disconnect();
            }
        }

        #endregion

        
        #region Public Delegates and Events
        /// <summary>
        /// The event handler of the event when data is received from the server
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="args">The event args, containing the string received</param>
        public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs args);
        public delegate void ErrorEventHandler(object sender, Exception exception);

        /// <summary>
        /// The event handler of the event when data is received from the server
        /// </summary>
        public event DataReceivedEventHandler DataReceivedHandler;
        public event ErrorEventHandler ErrorHandler;

        #endregion

        #region Constructor
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="host">The hostname</param>
        /// <param name="id">The user's id</param>
        /// <param name="pwd">The user's password</param>
        /// <param name="conType">Connection type</param>
        /// <param name="portNum">The host's port number</param>
        public Communicator(string host, string id, string pwd, ConnectionType conType, int portNum)
        {
            this.hostName = host;
            this.userId = id;
            this.password = pwd;
            this.connectionType = conType;
            this.port = portNum;
        }
        #endregion

        #region Fields
        private ConnectionType connectionType;
        private string hostName;
        private int port;
        private string userId;
        private string password;

        private System.AsyncCallback callbackProc;
        private System.AsyncCallback callbackEndDispatch;
        private System.Net.Sockets.Socket CurSocket;

        

        private Reader reader;

        #endregion

        #region Private Methods
        private void ConnectTelnet()
        {

            try
            {
            //System.Net.IPHostEntry IPHost  = System.Net.Dns.GetHostEntry(HostName); 
            System.Net.IPHostEntry IPHost = System.Net.Dns.GetHostEntry(hostName);
            System.Net.IPAddress[] addr = IPHost.AddressList;
			int ind = 0;
			while ("InterNetwork" != addr[ind].AddressFamily.ToString())
				ind++;

                // Create New Socket 
                this.CurSocket = new System.Net.Sockets.Socket(
                    System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream,
                    System.Net.Sockets.ProtocolType.Tcp);

                // Create New EndPoint
                System.Net.IPEndPoint iep = new System.Net.IPEndPoint(addr[ind], port);

                // This is a non blocking IO
                this.CurSocket.Blocking = false;

                // Begin Asyncronous Connection
                this.CurSocket.BeginConnect(iep, new System.AsyncCallback(ConnectCallback), CurSocket);
            }
            catch (System.Exception CurException)
            {
                //System.Console.WriteLine ("Connect: " + CurException.Message);
                this.OnError(CurException);
            }
        }

        private void ConnectSSH1()
        {
            // connect ssh

            
            try
            {
                Routrek.SSHC.SSHConnection _conn;
                Routrek.SSHC.SSHConnectionParameter f = new Routrek.SSHC.SSHConnectionParameter();
                f.UserName = userId;
                f.Password = password;
                f.Protocol = Routrek.SSHC.SSHProtocol.SSH1;

                f.AuthenticationType = Routrek.SSHC.AuthenticationType.Password;
                f.WindowSize = 0x1000;
                this.reader = new Reader(this);
                Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //s.Blocking = false;
                IPAddress ip;

                System.Net.IPAddress[] addr = Dns.GetHostEntry(hostName).AddressList;
				int ind = 0;
				while ("InterNetwork" != addr[ind].AddressFamily.ToString())
					ind++;
				ip = addr[ind];

                s.Connect(new IPEndPoint(ip, this.port));
                _conn = Routrek.SSHC.SSHConnection.Connect(f, reader, s);
                reader._conn = _conn;
                Routrek.SSHC.SSHChannel ch = _conn.OpenShell(reader);
                reader._pf = ch;
                Routrek.SSHC.SSHConnectionInfo ci = _conn.ConnectionInfo;
            }
            catch (Exception CurException)
            {
                this.OnError(CurException);                
            }

            //s.Connect(new IPEndPoint(IPAddress.Parse("0.0.0.0"), 22));
           
        }

        private void ConnectCallback(System.IAsyncResult ar)
        {
            try
            {
                // Get The connection socket from the callback
                System.Net.Sockets.Socket sock1 = (System.Net.Sockets.Socket)ar.AsyncState;

                if (sock1.Connected)
                {
                    CommsStateObject StateObject = new CommsStateObject();

                    StateObject.Socket = sock1;

                    // Assign Callback function to read from Asyncronous Socket
                    callbackProc = new System.AsyncCallback(OnReceivedData);

                    // Begin reading data asyncronously
                    sock1.BeginReceive(StateObject.Buffer, 0, StateObject.Buffer.Length,
                        System.Net.Sockets.SocketFlags.None, callbackProc, StateObject);
                }
            }

            catch (System.Exception CurException)
            {
                //System.Console.WriteLine ("ConnectCallback: " + CurException.Message);
                this.OnError(CurException);
            }
        }

        private void DoNothing(System.IAsyncResult ar)
        {

        }

        private void OnReceivedData(System.IAsyncResult ar)
        {
            try
            {
                // Get The connection socket from the callback
                CommsStateObject StateObject = (CommsStateObject)ar.AsyncState;

                // Get The data , if any
                int nBytesRec = StateObject.Socket.EndReceive(ar);

                if (nBytesRec > 0)
                {
                    //string sReceived = "";


                    //sReceived = System.Text.Encoding.GetEncoding("gb2312").GetString(
                    //    StateObject.Buffer, 0, nBytesRec);

                    //this.recBuffer += sReceived;

                    byte[] data = new byte[nBytesRec];
                    for (int i = 0; i < nBytesRec; i++)
                        data[i] = StateObject.Buffer[i];

                    this.OnReceivedData(data);

                    //StateObject.Socket.r

                    //Console.WriteLine("[start]( " + sReceived + " )[end]");
                    // Re-Establish the next asyncronous receveived data callback as
                    StateObject.Socket.BeginReceive(StateObject.Buffer, 0, StateObject.Buffer.Length,
                        System.Net.Sockets.SocketFlags.None, new System.AsyncCallback(OnReceivedData), StateObject);
                }
                else
                {
                    // If no data was recieved then the connection is probably dead
                    //System.Console.WriteLine ("Disconnected", StateObject.Socket.RemoteEndPoint);

                    /*if (this.DataRecHandler != null)
                            this.DataRecHandler(this, new DataReceivedEventArgs(recBuffer));

                    Console.WriteLine("[start]( " + recBuffer + " )[end]");
                    recBuffer = "";*/

                    StateObject.Socket.Shutdown(System.Net.Sockets.SocketShutdown.Both);

                    StateObject.Socket.Close();
                }
            }
            catch (Exception curException)
            {
                this.OnError(curException);
            }
        }

        

        private void EndDispatchMessage(System.IAsyncResult ar)
        {
            try
            {
                System.Net.Sockets.Socket Sock = (System.Net.Sockets.Socket)ar.AsyncState;

                Sock.EndSend(ar);
            }
            catch (System.Exception CurException)
            {
                //System.Console.WriteLine ("EndDispatchMessage: " + CurException.Message);
                //MessageBox.Show("EndDispatchMessage: " + CurException.Message);
                this.OnError(CurException);
            }
        }
        #endregion

        #region private class
        /// <summary>
        /// Communication State
        /// </summary>
        private class CommsStateObject
        {
            public System.Net.Sockets.Socket Socket;
            public System.Byte[] Buffer;

            public CommsStateObject()
            {
                this.Buffer = new System.Byte[65536];
            }
        }

        /// <summary>
        /// Routrek SSH Reader Class
        /// </summary>
        class Reader : Routrek.SSHC.ISSHConnectionEventReceiver, Routrek.SSHC.ISSHChannelEventReceiver
        {
            private Communicator owner;

            public Reader(Communicator wrapper)
            {
                this.owner = wrapper;
            }
            public Routrek.SSHC.SSHConnection _conn;
            public bool _ready;

            public void Disconnect()
            {
                if (_ready)
                _conn.Disconnect("");
            }

            public void OnData(byte[] data, int offset, int length)
            {
                //Terminal.Write(data, offset, length);
                //string str = System.Text.Encoding.GetEncoding("gb2312").GetString(data, offset, length);
                byte[] bytes = new byte[length];
                for (int i = 0; i < length; i++)
                    bytes[i] = data[offset + i];

                    owner.OnReceivedData(bytes);
                //rtb.AppendText(Encoding.GetEncoding("gb2312").GetString(data, offset, length));
                //System.Console.Write(Encoding.GetEncoding("gb2312").GetString(data, offset, length));
            }
            public void OnDebugMessage(bool always_display, byte[] data)
            {
                //Debug.WriteLine("DEBUG: "+ Encoding.GetEncoding("gb2312").GetString(data));
            }
            public void OnIgnoreMessage(byte[] data)
            {
                //Debug.WriteLine("Ignore: "+ Encoding.GetEncoding("gb2312").GetString(data));
            }
            public void OnAuthenticationPrompt(string[] msg)
            {
                //Debug.WriteLine("Auth Prompt "+msg[0]);
            }

            public void OnError(Exception error, string msg)
            {
                //Debug.WriteLine("ERROR: "+ msg);
                owner.OnError(error);
            }
            public void OnChannelClosed()
            {
                //Debug.WriteLine("Channel closed");
                _conn.Disconnect("");
                //_conn.AsyncReceive(this);
            }
            public void OnChannelEOF()
            {
                _pf.Close();
                //Debug.WriteLine("Channel EOF");
            }
            public void OnExtendedData(int type, byte[] data)
            {
                //Debug.WriteLine("EXTENDED DATA");
            }
            public void OnConnectionClosed()            
            {
                _ready = false;
                //System.Diagnostics.Debug.WriteLine("Connection closed");
                owner.OnError(new Exception("Connection closed"));
            }
            public void OnUnknownMessage(byte type, byte[] data)
            {
                //Debug.WriteLine("Unknown Message " + type);
            }
            public void OnChannelReady()
            {
                _ready = true;
            }
            public void OnChannelError(Exception error, string msg)
            {
                //Debug.WriteLine("Channel ERROR: "+ msg);
            }
            public void OnMiscPacket(byte type, byte[] data, int offset, int length)
            {
            }

            public Routrek.SSHC.PortForwardingCheckResult CheckPortForwardingRequest(string host, int port, string originator_host, int originator_port)
            {
                Routrek.SSHC.PortForwardingCheckResult r = new Routrek.SSHC.PortForwardingCheckResult();
                r.allowed = true;
                r.channel = this;
                return r;
            }
            public void EstablishPortforwarding(Routrek.SSHC.ISSHChannelEventReceiver rec, Routrek.SSHC.SSHChannel channel)
            {
                _pf = channel;
            }

            public Routrek.SSHC.SSHChannel _pf;

        }
        #endregion

    }

    
}
